package reading.service;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import reading.common.constant.SpecialStringConstant;
import reading.common.domain.AuthToken;
import reading.common.enums.ErrorEnum;
import reading.common.exception.*;
import reading.common.setting.SysSetting;
import reading.data.mapper.SysFileMapper;
import reading.data.model.SysFile;
import reading.util.aes.Encry;
import reading.util.utils.DateUtil;
import reading.util.utils.StringUtil;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author TXW
 * @version V1.0
 * @Title: ${file_name}
 * @date 2018/9/17 21:54
 */
@Service
public class FileService extends AbstractBaseService<SysFile> {
    private static final Logger logger = LoggerFactory.getLogger(FileService.class);
    private final SysSetting sysSetting;
    private final SysFileMapper sysFileMapper;
    private final int BUFFER_SIZE = 1024;

    @Autowired
    public FileService(SysSetting sysSetting, SysFileMapper sysFileMapper) {
        this.sysSetting = sysSetting;
        this.sysFileMapper = sysFileMapper;
        super.setBaseDbMapper(sysFileMapper);
    }

    public Map getInputStream(Long id) throws IOException {
        Map<String, Object> map = new HashMap<>(2);
        SysFile resource = sysFileMapper.findById(id);
        map.put("f", resource);
        Path path = Paths.get(resource.getPath());
        map.put("s", Files.newInputStream(path));
        return map;
    }

    @Override
    public SysFile findById(Long id) {
        return sysFileMapper.findById(id);
    }

    public Map<String, Object> trans(SysFile resource) {
        Map<String, Object> resultMap = new HashMap<>(4);
        resultMap.put("state", "SUCCESS");
        resultMap.put("original", resource.getOriginName() + "." + resource.getSuffixes());
        resultMap.put("type", resource.getSuffixes());

        String url = "/file/download" + "/" + resource.getId();
        resultMap.put("url", url);
        resultMap.put("uploaded", true);
        resultMap.put("coverId", resource.getId());
        return resultMap;
    }

    @Transactional(rollbackFor = HlDbOptException.class)
    public SysFile fileUpload(MultipartFile multipartFile, String status) throws IOException {
        String oFilename = multipartFile.getOriginalFilename();
        final int maxSize = 100;
        if (oFilename.length() > maxSize) {
            throw new HlValidateException(400004, "原始文件名太长");
        }

        String hash = Encry.md5(multipartFile.getBytes());
        String sub = hash.substring(0, 2);

        SysFile resource = new SysFile();
        //配置上传路径/年/月/hash前两位值(例: /2018/2/6a)
        String subPath = createUploadPath(sub);
        String baseName = FilenameUtils.getBaseName(oFilename);
        String fileType = FilenameUtils.getExtension(oFilename);
        resource.setOriginName(baseName);
        resource.setSuffixes(fileType);
        String newFileName = generateFileName(resource.getSuffixes());
        String realPath = subPath + File.separator + newFileName;

        resource.setCreateTime(OffsetDateTime.now());
        resource.setStatus(status);
        resource.setPath(realPath);
        createDir(subPath);
        multipartFile.transferTo(new File(realPath));
        sysFileMapper.create(resource);
        return resource;
    }


    /**
     * 使用currentTimeMillis作为文件名
     *
     * @param fileType
     * @return
     */
    private String generateFileName(String fileType) {
        long uuid = System.currentTimeMillis();
        if (StringUtil.isNotBlank(fileType)) {
            return uuid + "." + fileType;
        } else {
            return String.valueOf(uuid);
        }
    }

    /**
     * 创建上传目录
     *
     * @param sub
     * @return
     */
    private String createUploadPath(String sub) {
        String rootPath = getUploadPath();
        String uploadPath = rootPath + File.separator + DateUtil.getYear() + File.separator + DateUtil.getMonth() + File.separator + sub;

        boolean success = createDir(rootPath);
        if (!success) {
            throw new HlIoException(800001, "上传目录创建失败");
        }
        return uploadPath;
    }

    /**
     * 如果目录不存在则创建之
     *
     * @param path
     * @return
     */
    private boolean createDir(String path) {
        File file = new File(path);
        if (file.exists()) {
            return true;
        } else {
            return file.mkdirs();
        }
    }

    /**
     * 根据系统获取上传路径
     *
     * @return
     */
    private String getUploadPath() {
        String uploadPath = sysSetting.getFileUploadPath();
        if (System.getProperty(SpecialStringConstant.OS_NAME).toLowerCase().startsWith(SpecialStringConstant.WIN)) {
            uploadPath = sysSetting.getFileWinUploadPath();
        }
        return uploadPath;
    }

    /**
     * 前端请求附件下载
     *
     * @param fileId   文件ID
     * @param response http response
     */
    public void downloadFile(String fileId, HttpServletResponse response) {
        Optional<AuthToken> authToken = AuthToken.getFromThreadLocal();
        if (!authToken.isPresent()) {
            throw new HlUnauthorizedException();
        }
        Long uId = authToken.get().ownerId;
        SysFile resource = sysFileMapper.findById(Long.valueOf(fileId));
        InputStream in;

        if (resource == null) {
            throw new HlBadRequestException(ErrorEnum.FILE_NOT_FIND);
        }

        try {
            in = getInputStream(resource);
        } catch (IOException e) {
            throw new HlBadRequestException(ErrorEnum.FILE_NOT_FIND);
        }

        if (in == null) {
            throw new HlBadRequestException(ErrorEnum.FILE_NOT_FIND);
        }

        // 设置请求头
        response.setContentType("multipart/form-data");
        response.setHeader("Content-Disposition", getHeaderStr(resource));
        try {
            writeFile(in, response.getOutputStream());
        } catch (IOException e) {
            logger.trace("文件下载失败", e);
        }
    }

    private void writeFile(InputStream in, OutputStream out) throws IOException {
        try {
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            out.flush();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
            }
            try {
                out.close();
            } catch (IOException e) {
            }
        }
    }

    private String getHeaderStr(SysFile file) {
        // 获取文件请求头 最后一个参数是设置下载文件名(假如我们叫a.pdf)
        StringBuilder sb = new StringBuilder("attachment;fileName=");

        try {
            sb.append(new String(file.getOriginName().getBytes("utf-8"), "iso-8859-1"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        String fileType = file.getSuffixes();

        if (fileType != null) {
            sb.append(".").append(file.getSuffixes());
        }

        return sb.toString();
    }

    private InputStream getInputStream(SysFile resource) throws IOException {
        Path path = Paths.get(resource.getPath());
        if (path != null) {
            return Files.newInputStream(path);
        } else {
            return null;
        }
    }

}
